Skip to content

feat(core): Export a reusable function to add tracing headers#20076

Merged
JPeer264 merged 4 commits intodevelopfrom
jp/export-trace-headers
Apr 9, 2026
Merged

feat(core): Export a reusable function to add tracing headers#20076
JPeer264 merged 4 commits intodevelopfrom
jp/export-trace-headers

Conversation

@JPeer264
Copy link
Copy Markdown
Member

@JPeer264 JPeer264 commented Apr 1, 2026

This PR is an extraction of #19991

It basically exports getTracingHeadersForFetchRequest, which was previously only exported for testing, but offers a great functionality if you want to add tracing headers to a request. I renamed it as addTracingHeadersToFetchRequest sounded a little misleading, as it didn't really add headers to the request, as it returned the extracted headers from the request (or init, if there are any).

Open question

I added @hidden and @internal to it, not sure if this is an approach we follow. I'm ok to remove it from the jsdoc

@JPeer264 JPeer264 requested review from Lms24 and nicohrubec April 1, 2026 16:59
@JPeer264 JPeer264 self-assigned this Apr 1, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 1, 2026

Semver Impact of This PR

🟡 Minor (new features)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


New Features ✨

Core

  • Export a reusable function to add tracing headers by JPeer264 in #20076
  • Support registerTool/registerResource/registerPrompt in MCP integration by betegon in #20071
  • Support embeddings in langchain by nicohrubec in #20017

Deps

  • Bump bundler plugins to 5.2.0 by chargome in #20122
  • Bump lodash.template from 4.5.0 to 4.18.1 by dependabot in #20085
  • Bump @xmldom/xmldom from 0.8.3 to 0.8.12 by dependabot in #20066

Other

  • (cloudflare) Support basic WorkerEntrypoint by JPeer264 in #19884
  • (core, node) Portable Express integration by isaacs in #19928
  • (deno) Add denoRuntimeMetricsIntegration by chargome in #20023
  • (node, bun) Enforce minimum collection interval in runtime metrics integrations by chargome in #20068
  • (react-router) Export sentryOnError by chargome in #20120

Bug Fixes 🐛

Core

  • Only attach flags context to error events by Lms24 in #20116
  • Replace regex with string check in stack parser to prevent main thread blocking by chargome in #20089
  • Set span.status to error when MCP tool returns JSON-RPC error response by betegon in #20082

Other

  • (angular) Bump TypeScript to ~6.0.0 in angular-21 E2E test app by andreiborza in #20134
  • (aws-serverless) Add timeout to _endSpan forceFlush to prevent Lambda hanging by logaretm in #20064
  • (cloudflare) Ensure every request instruments functions by JPeer264 in #20044
  • (gatsby) Fix errorHandler signature to match bundler-plugin-core API by JPeer264 in #20048

Internal Changes 🔧

Core

  • Do not emit spans for chats.create in google-genai by nicohrubec in #19990
  • Unify .do* span ops to gen_ai.generate_content by nicohrubec in #20074
  • Simplify addResponseAttributes in openai integration by nicohrubec in #20013
  • Extract shared endStreamSpan for AI integrations by nicohrubec in #20021
  • Remove provider-specific AI span attributes in favor of gen_ai attributes in sentry conventions by nicohrubec in #20011

Deps

  • Bump mshick/add-pr-comment from dd126dd8c253650d181ad9538d8b4fa218fc31e8 to e7516d74559b5514092f5b096ed29a629a1237c6 by dependabot in #20078
  • Bump getsentry/craft/.github/workflows/changelog-preview.yml from 2.24.1 to 2.25.2 by dependabot in #20081

Other

  • (node) Add node integration tests for Vercel ToolLoopAgent by nicohrubec in #20087
  • (nuxt) Make Nuxt 5 (nightly) E2E optional by s1gr1d in #20113
  • (oxlint) Add typeawareness into oxlintrc by JPeer264 in #20075
  • Update validate-pr workflow by stephanie-anderson in #20072
  • Remove unused tsconfig-template folder by mydea in #20067

🤖 This preview updates automatically when you update the PR.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 1, 2026

size-limit report 📦

⚠️ Warning: Base artifact is not the latest one, because the latest workflow run is not done yet. This may lead to incorrect results. Try to re-run all tests to get up to date results.

Path Size % Change Change
@sentry/browser 25.64 kB - -
@sentry/browser - with treeshaking flags 24.13 kB - -
@sentry/browser (incl. Tracing) 42.19 kB +0.1% +39 B 🔺
@sentry/browser (incl. Tracing, Profiling) 46.8 kB +0.08% +34 B 🔺
@sentry/browser (incl. Tracing, Replay) 80.97 kB +0.04% +32 B 🔺
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 70.59 kB +0.05% +29 B 🔺
@sentry/browser (incl. Tracing, Replay with Canvas) 85.69 kB +0.04% +33 B 🔺
@sentry/browser (incl. Tracing, Replay, Feedback) 97.95 kB +0.04% +32 B 🔺
@sentry/browser (incl. Feedback) 42.42 kB - -
@sentry/browser (incl. sendFeedback) 30.31 kB - -
@sentry/browser (incl. FeedbackAsync) 35.3 kB - -
@sentry/browser (incl. Metrics) 26.95 kB - -
@sentry/browser (incl. Logs) 27.1 kB - -
@sentry/browser (incl. Metrics & Logs) 27.78 kB - -
@sentry/react 27.41 kB - -
@sentry/react (incl. Tracing) 44.52 kB +0.09% +37 B 🔺
@sentry/vue 30.08 kB - -
@sentry/vue (incl. Tracing) 44.08 kB +0.08% +33 B 🔺
@sentry/svelte 25.67 kB - -
CDN Bundle 28.32 kB - -
CDN Bundle (incl. Tracing) 43.15 kB +0.07% +29 B 🔺
CDN Bundle (incl. Logs, Metrics) 29.69 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) 44.2 kB +0.06% +26 B 🔺
CDN Bundle (incl. Replay, Logs, Metrics) 68.49 kB - -
CDN Bundle (incl. Tracing, Replay) 80.04 kB +0.04% +29 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) 81.1 kB +0.06% +44 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback) 85.59 kB +0.05% +35 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) 86.62 kB +0.05% +35 B 🔺
CDN Bundle - uncompressed 82.72 kB - -
CDN Bundle (incl. Tracing) - uncompressed 127.99 kB +0.11% +133 B 🔺
CDN Bundle (incl. Logs, Metrics) - uncompressed 86.86 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed 131.4 kB +0.11% +133 B 🔺
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed 209.84 kB - -
CDN Bundle (incl. Tracing, Replay) - uncompressed 244.87 kB +0.06% +133 B 🔺
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed 248.27 kB +0.06% +133 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 257.78 kB +0.06% +133 B 🔺
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed 261.17 kB +0.06% +133 B 🔺
@sentry/nextjs (client) 46.93 kB +0.09% +39 B 🔺
@sentry/sveltekit (client) 42.66 kB +0.09% +35 B 🔺
@sentry/node-core 55.76 kB +0.02% +10 B 🔺
@sentry/node 172.41 kB +0.01% +13 B 🔺
@sentry/node - without tracing 96.04 kB +0.01% +5 B 🔺
@sentry/aws-serverless 112.85 kB +0.01% +6 B 🔺

View base workflow run

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 1, 2026

node-overhead report 🧳

Note: This is a synthetic benchmark with a minimal express app and does not necessarily reflect the real-world performance impact in an application.

Scenario Requests/s % of Baseline Prev. Requests/s Change %
GET Baseline 9,844 - 9,316 +6%
GET With Sentry 1,769 18% 1,744 +1%
GET With Sentry (error only) 5,942 60% 5,995 -1%
POST Baseline 1,196 - 1,211 -1%
POST With Sentry 599 50% 624 -4%
POST With Sentry (error only) 1,060 89% 1,056 +0%
MYSQL Baseline 3,289 - 3,337 -1%
MYSQL With Sentry 486 15% 499 -3%
MYSQL With Sentry (error only) 2,667 81% 2,666 +0%

View base workflow run

type PolymorphicRequestHeaders =
| Record<string, string | undefined>
| Array<[string, string]>
| Iterable<Iterable<string>>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accepted Iterable type lacks runtime handling in function

Low Severity

The new Iterable<Iterable<string>> variant was added to PolymorphicRequestHeaders, which is used as the type for fetchOptionsObj.headers. However, the function body only handles Headers instances (via isHeaders), arrays (via Array.isArray), and plain objects (the else branch). A non-array, non-Headers iterable (e.g. a Map) would silently fall into the else branch, where in operator checks and object spread would not correctly process the iterable's entries, producing malformed headers. Since this function is now exported for cross-package use, the type promises broader acceptance than the implementation supports.

Additional Locations (1)
Fix in Cursor Fix in Web

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This type is needed for Cloudflare headers. I've added a test to also check for that case.

@JPeer264 JPeer264 requested a review from andreiborza April 7, 2026 06:35
@JPeer264 JPeer264 force-pushed the jp/export-trace-headers branch from d9322e7 to 1622527 Compare April 7, 2026 08:52
Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Autofix Details

Bugbot Autofix prepared a fix for the issue found in the latest run.

  • ✅ Fixed: feat PR lacks integration or E2E test
    • Added a Node.js integration test that demonstrates cross-package usage of getTracingHeadersForFetchRequest, validating that the function correctly generates tracing headers when imported from @sentry/core.

Create PR

Or push these changes by commenting:

@cursor push 7c341385d0
Preview (7c341385d0)
diff --git a/dev-packages/node-integration-tests/suites/public-api/getTracingHeadersForFetchRequest/basic-usage/scenario.ts b/dev-packages/node-integration-tests/suites/public-api/getTracingHeadersForFetchRequest/basic-usage/scenario.ts
new file mode 100644
--- /dev/null
+++ b/dev-packages/node-integration-tests/suites/public-api/getTracingHeadersForFetchRequest/basic-usage/scenario.ts
@@ -1,0 +1,30 @@
+import * as Sentry from '@sentry/node';
+import { getTracingHeadersForFetchRequest } from '@sentry/core';
+import { loggingTransport } from '@sentry-internal/node-integration-tests';
+
+Sentry.init({
+  dsn: 'https://public@dsn.ingest.sentry.io/1337',
+  tracesSampleRate: 1.0,
+  transport: loggingTransport,
+});
+
+Sentry.startSpan({ name: 'test-span' }, () => {
+  const headers = getTracingHeadersForFetchRequest('https://example.com/api', {});
+
+  if (!headers || typeof headers !== 'object' || Array.isArray(headers)) {
+    throw new Error('getTracingHeadersForFetchRequest returned invalid headers');
+  }
+
+  const sentryTrace = 'sentry-trace' in headers ? headers['sentry-trace'] : undefined;
+  const baggage = 'baggage' in headers ? headers.baggage : undefined;
+
+  if (!sentryTrace || typeof sentryTrace !== 'string' || !sentryTrace.match(/^[0-9a-f]{32}-[0-9a-f]{16}-[01]$/)) {
+    throw new Error('sentry-trace header is invalid or missing');
+  }
+
+  if (!baggage || typeof baggage !== 'string' || !baggage.includes('sentry-')) {
+    throw new Error('baggage header is invalid or missing');
+  }
+
+  Sentry.captureMessage('test message');
+});

diff --git a/dev-packages/node-integration-tests/suites/public-api/getTracingHeadersForFetchRequest/basic-usage/test.ts b/dev-packages/node-integration-tests/suites/public-api/getTracingHeadersForFetchRequest/basic-usage/test.ts
new file mode 100644
--- /dev/null
+++ b/dev-packages/node-integration-tests/suites/public-api/getTracingHeadersForFetchRequest/basic-usage/test.ts
@@ -1,0 +1,17 @@
+import { afterAll, test } from 'vitest';
+import { cleanupChildProcesses, createRunner } from '../../../../utils/runner';
+
+afterAll(() => {
+  cleanupChildProcesses();
+});
+
+test('should generate tracing headers for fetch request', async () => {
+  await createRunner(__dirname, 'scenario.ts')
+    .expect({
+      event: {
+        message: 'test message',
+      },
+    })
+    .start()
+    .completed();
+});

This Bugbot Autofix run was free. To enable autofix for future PRs, go to the Cursor dashboard.

@JPeer264 JPeer264 requested review from chargome and s1gr1d and removed request for Lms24 and nicohrubec April 8, 2026 08:44
@chargome
Copy link
Copy Markdown
Member

chargome commented Apr 8, 2026

q: IIC you want to use this in Cloudflare afterwards? I think we have serveral conventions for internal functions but lately we've been making use of an _INTERNAL_ prefix so it is clear right away that this function is not meant for public use

@JPeer264
Copy link
Copy Markdown
Member Author

JPeer264 commented Apr 8, 2026

Yes for now only for Cloudflare, but in theory this can be then used for other instrumentations then. _INTERNAL_ is a good one, this prefix seems to be exported already from core. I'll stick to that, remove the @hidden, but keep @internal

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 8100e43. Configure here.

@JPeer264 JPeer264 force-pushed the jp/export-trace-headers branch from 8100e43 to 4e235e3 Compare April 8, 2026 15:47
* 3. Previous baggage header has sentry baggage values -> do nothing (might have been added manually by users)
* @internal Exported for cross-package instrumentation (for example Cloudflare Workers fetcher bindings)
* and unit tests
* @hidden
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @hidden

very logaf-L


return headers.every(
(item): item is [string, string] =>
Array.isArray(item) && item.length === 2 && typeof item[0] === 'string' && typeof item[1] === 'string',
Copy link
Copy Markdown
Member

@Lms24 Lms24 Apr 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

l/m: I think this added condition is too narrow. A header value doesn't have to be a string. I just tried a request like

fetch('https://github.com', {headers: [['x-test', 2]]})

in the chrome dev console and chrome sent the request with the added header value. So looks like this gets coerced to a string if it's not one in the first place. Might be relevant IRL for cache ttl or body size headers, if users add them manually 🤔

We can probably just omit the typeof check for the 2nd value, wdyt?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

True, but then the types were wrong before, I'll update them to also match other values. Dropping the second value would make sense in this case.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just made the second value to be unknown. It is still worth to mention that undici types (and types from lib.dom.d.ts had the same types. But making it more general overall is safer.

Screenshot 2026-04-09 at 13 04 46

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

huh interesting, thanks for checking the types! Tbh this makes it logaf-L for me then but let's better be safe than sorry.

Comment on lines +22 to +24
| Record<string, unknown>
| Array<[string, unknown]>
| Iterable<Iterable<unknown>>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The function _INTERNAL_getTracingHeadersForFetchRequest doesn't handle Iterable header types like Map, causing user-provided headers to be silently discarded when passed.
Severity: HIGH

Suggested Fix

Add a new branch to _INTERNAL_getTracingHeadersForFetchRequest to handle Iterable<Iterable<unknown>> types. This branch should convert the iterable into a Headers object or a plain object before merging it with the Sentry tracing headers. For example, new Headers(originalHeaders) can be used to correctly process the iterable.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: packages/core/src/fetch.ts#L22-L24

Potential issue: The `PolymorphicRequestHeaders` type was expanded to include
`Iterable<Iterable<unknown>>` to support environments like Cloudflare Workers. However,
the implementation of `_INTERNAL_getTracingHeadersForFetchRequest` was not updated to
handle this case. When an iterable that is not an array (like a `Map`) is passed as
headers, it falls into an `else` block that incorrectly assumes it's a plain object.
Using the spread operator (`...`) on a `Map` to create a new object results in an empty
object, causing all original headers to be silently dropped from the request.

@JPeer264 JPeer264 requested a review from Lms24 April 9, 2026 11:25
@JPeer264 JPeer264 merged commit 45fa810 into develop Apr 9, 2026
474 of 476 checks passed
@JPeer264 JPeer264 deleted the jp/export-trace-headers branch April 9, 2026 18:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants